/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */
package com.obixlabs.commons.types;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * <p>
 * This class serves as an in-memory lookup/cache of {@link ObixSimpleType simple type} values. It is 
 * provided for {@link ObixSimpleType simple types} which are generally static, e.g. business or 
 * process codes,  and, as such, do not change frequently. Consequently such types can be initialised 
 * once during the lifetime of the application, and referenced from an in-memory store rather 
 * thus removing the need to constantly hit the data-access or storage layer of the application.
 * </p>
 * 
 * <p>
 * Subclasses of this type need only implement the {@link #initData()} method to handle
 * the specifics of loading the {@link ObixSimpleType type} values to be stored. This
 * could involve accessing persistent storage such as a file or database, or it could 
 * involve initialisation via configuration or code. 
 * </p>
 * 
 * <p>
 * <b>Note:</b> This class is thread-safe.
 * </p>
 * 
 * @param <T>   The {@link ObixSimpleType simple type} to be stored.
 */
public abstract class ObixSimpleTypeLookup<T extends ObixSimpleType> implements Serializable
{
	private static final long serialVersionUID = -6113697319813126464L;
	
	/**
	 * <p>
	 * Internal storage and lookup {@link Map map}. 
	 * </p>
	 */
	private Map<String, T> contents;
	
	/**
	 * <p>
	 * Internal {@link ReadWriteLock rw lock} used for concurrency control. 
	 * </p>
	 */
	private ReadWriteLock readWriteLock;
	
	/**
	 * <p>
	 * Default constructor.
	 * </p>
	 */
	public ObixSimpleTypeLookup()
	{
		contents = new HashMap<String, T>();
		readWriteLock = new ReentrantReadWriteLock();
	}
	
	/**
	 * <p>
	 * Initialises the contents of an instance. This method should always be invoked before 
	 * use. It relies on the implementation of the {@link #initData()} method for the actual 
	 * loading specifics. 
	 * </p>
	 */
	public void init()
	{
		lockForWrite();		
		try {
			contents.clear();			
			Set<T> data = initData();			
			for (T type : data) contents.put(type.getName(), type);
		} finally {unlockAfterWrite();}
	}
	
	/**
	 * <p>
	 * Returns a read-only view of all the 
	 * {@link ObixSimpleType type} values stored 
	 * in this instance.
	 * </p>
	 *  
	 * @return A {@link Collection collection} containing 
	 * all the types stored in this instance.
	 */
	public Collection<T> allTypes()
	{return Collections.unmodifiableCollection(contents.values());}
	
	/**
	 * <p>
	 * Returns a read-only {@link Set set} containing 
	 * the {@link ObixSimpleType#getName() names} of all 
	 * the {@link ObixSimpleType types} stored in this
	 * instance.
	 * </p>
	 * 
	 * @return	A read-only {@link Set set} of 
	 * {@link ObixSimpleType#getName() type names}, 
	 * as stored in this instance. 
	 */
	public Set<String> allKeys()
	{return contents.keySet();}
	
	/**
	 * <p>
	 * Returns the {@link ObixSimpleType type} with the given {@link ObixSimpleType#getName() name} from 
	 * the internal cache. This method should only be invoked after the {@link #init()} method has been 
	 * successfully executed, as it will return <code>null</code> otherwise.
	 * </p>
	 * 
	 * @param name The {@link ObixSimpleType#getName() name} of the {@link ObixSimpleType type}
	 * to lookup.
	 * 
	 * @return     The {@link ObixSimpleType type} with the given {@link ObixSimpleType#getName() name}, if stored 
	 * internally within this instance and <code>null</code> otherwise.  
	 */
	public T lookup(String name)
	{
		lockForRead();
		try {
			return contents.get(name);
		} finally {unlockAfterRead();}
	}
	
	/**
	 * <p>
	 * Handles the specifics of loading the {@link ObixSimpleType simple type} data. In a typical 
	 * implementation, this would involve reading data from persistent storage, the file-system or 
	 * configuration. 
	 * </p>
	 * @return     A {@link Set set} containing all of the {@link ObixSimpleType types} to be stored
	 * in this instance. This is the value with which the {@link #init()} method initialises the internal
	 * lookup cache.  
	 */
	protected abstract Set<T> initData();
	
	/**
	 * <p>
	 * Locks an instance for reading.
	 * </p>
	 */
	private void lockForRead() {readWriteLock.readLock().lock();}
	
        /**
         * <p>
         * Releases a read-lock on an instance.
         * </p>
         */	
	private void unlockAfterRead() {readWriteLock.readLock().unlock();}

        /**
         * <p>
         * Acquires the write lock for an instance.
         * </p>
         */	
	private void lockForWrite(){readWriteLock.writeLock().lock();}
	
        /**
         * <p>
         * Releases the write lock for the instance.
         * </p>
         */	
	private void unlockAfterWrite(){readWriteLock.writeLock().unlock();}
}